/*
 * Board initialize code for Loongson1.
 *
 * (C) 2014 Tang Haifeng <tanghaifeng-gz@loongson.cn>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2, or (at
 * your option) any later version.
 */

#include <config.h>
#include <common.h>
#include <command.h>
#include <malloc.h>
#include <miiphy.h>
#include <netdev.h>
#include <asm/io.h>
#include <asm/reboot.h>
#include <asm/gpio.h>

#include <asm/ls1x.h>
#include <asm/arch/regs-clk.h>

DECLARE_GLOBAL_DATA_PTR;

#if defined(CONFIG_SHOW_BOOT_PROGRESS)
void show_boot_progress(int progress)
{
	printf("Boot reached stage %d\n", progress);
}
#endif

void _machine_restart(void)
{
	int wdt_base = LS1X_WDT_BASE;

	writel(1, wdt_base + WDT_EN);
	writel(0x5000000, wdt_base + WDT_TIMER);
	writel(1, wdt_base + WDT_SET);

	while (1) {
		__asm__(".set push;\n"
			".set mips3;\n"
			"wait;\n"
			".set pop;\n"
		);
	}
}

phys_size_t initdram(int board_type)
{
	return get_ram_size ((long *)CONFIG_SYS_SDRAM_BASE, CONFIG_MEM_SIZE);
}

int checkboard(void)
{
	set_io_port_base(0x0);

	printf("checkboard\n");
	printf("Board: %s ", CONFIG_BOARD_NAME);
	printf("(CPU Speed %ld MHz/ Mem @ %ld MHz/ Bus @ %ld MHz)\n", gd->cpu_clk/1000000, gd->mem_clk/1000000, gd->bus_clk/1000000);

	return 0;
}

extern int gmac_initialize(ulong base_addr, u32 interface);

static void ls1x_set_gmac(void)
{
#if defined(CONFIG_CPU_LOONGSON1A)
#ifdef LOONGSON_GMAC0_EN
	*((volatile unsigned int*)0xbfd00420) &= ~0x00800000;	/* 使能GMAC0 */
	#ifdef LOONGSON_GMAC0_100M
	*((volatile unsigned int*)0xbfd00420) |= 0x500;		/* 配置成百兆模式 */
	#else
	*((volatile unsigned int*)0xbfd00420) &= ~0x500;		/* 否则配置成千兆模式 */
	#endif
#endif
#ifdef LOONGSON_GMAC1_EN
	*((volatile unsigned int*)0xbfd00420) &= ~0x01000000;	/* 使能GMAC1 */
	#ifdef LOONGSON_GMAC1_100M
	*((volatile unsigned int*)0xbfd00420) |= 0xa00;		/* 配置成百兆模式 */
	#else
	*((volatile unsigned int*)0xbfd00420) &= ~0xa00;		/* 否则配置成千兆模式 */
	#endif
	#ifdef GMAC1_USE_UART01
	*((volatile unsigned int*)0xbfd00420) |= 0xc0;
	#else
	*((volatile unsigned int*)0xbfd00420) &= ~0xc0;
	#endif
#endif
#elif defined(CONFIG_CPU_LOONGSON1B)
	/* 寄存器0xbfd00424有GMAC的使能开关 */
#ifdef LOONGSON_GMAC0_EN
	*((volatile unsigned int*)0xbfd00424) &= ~0x1000;	/* 使能GMAC0 */
	#ifdef LOONGSON_GMAC0_100M
	*((volatile unsigned int*)0xbfd00424) |= 0x5;		/* 配置成百兆模式 */
	#else
	*((volatile unsigned int*)0xbfd00424) &= ~0x5;	/* 否则配置成千兆模式 */
	#endif
#endif
	/* GMAC1初始化 使能GMAC1 和UART0复用，导致UART0不能使用 */
#ifdef LOONGSON_GMAC1_EN
	*((volatile unsigned int*)0xbfd00420) |= 0x18;
	*((volatile unsigned int*)0xbfd00424) &= ~0x2000;	/* 使能GMAC1 */
	#ifdef LOONGSON_GMAC1_100M
	*((volatile unsigned int*)0xbfd00424) |= 0xa;		/* 配置成百兆模式 */
	#else
	*((volatile unsigned int*)0xbfd00424) &= ~0xa;	/* 否则配置成千兆模式 */
	#endif
#endif
#elif defined(CONFIG_CPU_LOONGSON1C)
	*((volatile unsigned int *)0xbfd00424) &= ~(7 << 28);
#ifdef LOONGSON_GMAC_RMII
	*((volatile unsigned int *)0xbfd00424) |= (1 << 30); //wl rmii
#endif
#endif
}

int board_eth_init(bd_t *bis)
{
	int ret = 0;

#ifdef CONFIG_RANDOM_MACADDR
	{
	uchar enetaddr[6];
	if (!eth_getenv_enetaddr("ethaddr", enetaddr)) {
		eth_random_enetaddr(enetaddr);
		if (eth_setenv_enetaddr("ethaddr", enetaddr)) {
			printf("Failed to set ethernet address\n");
		} else {
			saveenv();
		}
	}

	if (!eth_getenv_enetaddr("eth1addr", enetaddr)) {
		eth_random_enetaddr(enetaddr);
		if (eth_setenv_enetaddr("eth1addr", enetaddr)) {
			printf("Failed to set ethernet address\n");
		} else {
			saveenv();
		}
	}
	}
#endif

#ifdef CONFIG_LOONGSON_GMAC
	ls1x_set_gmac();
#ifdef LOONGSON_GMAC0_EN
	ret = gmac_initialize(LS1X_GMAC0_BASE, LOONGSON_GMAC_PHY_MODE);
#endif
#ifdef LOONGSON_GMAC1_EN
	ret = gmac_initialize(LS1X_GMAC1_BASE, LOONGSON_GMAC_PHY_MODE);
#endif
#endif

#if defined(CONFIG_DESIGNWARE_ETH)
	u32 interface = PHY_INTERFACE_MODE_MII;
	if (designware_initialize(0, LS1X_GMAC0_BASE, CONFIG_DW0_PHY,
				interface) >= 0)
		ret++;
#endif
	return ret;
}

static void calc_clocks(void)
{
#if defined(CONFIG_CPU_LOONGSON1A)
	{
		gd->cpu_clk = CPU_CLOCK_RATE;
		gd->mem_clk = APB_CLOCK_RATE;
		gd->bus_clk = gd->mem_clk / 2;
	}
#elif defined(CONFIG_CPU_LOONGSON1B)
	{
		unsigned long pll_freq;
		unsigned int pll = readl(LS1X_CLK_PLL_FREQ);
		unsigned int ctrl = readl(LS1X_CLK_PLL_DIV);
		pll_freq = (12 + (pll & 0x3f)) * OSC_CLK / 2 + ((pll >> 8) & 0x3ff) * OSC_CLK / 1024 / 2;
		gd->cpu_clk = pll_freq / ((ctrl & DIV_CPU) >> DIV_CPU_SHIFT);
		gd->mem_clk = pll_freq / ((ctrl & DIV_DDR) >> DIV_DDR_SHIFT);
		gd->bus_clk = gd->mem_clk / 2;
		gd->arch.pll_clk = pll_freq;
	}
#elif defined(CONFIG_CPU_LOONGSON1C)
	{
		unsigned long pll_freq;
		unsigned int pll = readl(LS1X_CLK_PLL_FREQ);
		unsigned int clk_div = readl(LS1X_CLK_PLL_DIV);
		pll_freq = ((pll >> 8) & 0xff) * OSC_CLK / 4;
		if (clk_div & DIV_CPU_SEL) {
			if(clk_div & DIV_CPU_EN) {
				gd->cpu_clk = pll_freq / ((clk_div & DIV_CPU) >> DIV_CPU_SHIFT);
			} else {
				gd->cpu_clk = pll_freq / 2;
			}
		} else {
			gd->cpu_clk = OSC_CLK;
		}
		gd->mem_clk = gd->cpu_clk / ((1 << ((pll_freq & 0x3) + 1)) % 5);
		gd->bus_clk = gd->mem_clk;
		gd->arch.pll_clk = pll_freq;
	}
#endif
}

int board_early_init_f(void)
{
	calc_clocks();

	return 0;
}

#include <pca953x.h>
#define CONFIG_PCA9554_ADDR	0x22
#define CONFIG_PCA9554_USB_RESET	22
#define CONFIG_PCA9554_GM8285_SHTDN	20
#define CONFIG_PCA9554_PHY0_RESET	5
#define CONFIG_PCA9554_PHY1_RESET	21
#define CONFIG_PCA9554_SHT31_RESET	23
#define CONFIG_PCA9554_LED	13
int power_init_board(void)
{
	#ifdef CONFIG_PCA953X
	pca953x_set_dir(CONFIG_PCA9554_ADDR,
	  1 << CONFIG_PCA9554_USB_RESET, PCA953X_DIR_OUT << CONFIG_PCA9554_USB_RESET);
	pca953x_set_val(CONFIG_PCA9554_ADDR,
	  1 << CONFIG_PCA9554_USB_RESET, PCA953X_OUT_HIGH << CONFIG_PCA9554_USB_RESET);

	pca953x_set_dir(CONFIG_PCA9554_ADDR,
	  1 << CONFIG_PCA9554_GM8285_SHTDN, PCA953X_DIR_OUT << CONFIG_PCA9554_GM8285_SHTDN);
	pca953x_set_val(CONFIG_PCA9554_ADDR,
	  1 << CONFIG_PCA9554_GM8285_SHTDN, PCA953X_OUT_HIGH << CONFIG_PCA9554_GM8285_SHTDN);

	pca953x_set_dir(CONFIG_PCA9554_ADDR,
	  1 << CONFIG_PCA9554_PHY0_RESET, PCA953X_DIR_OUT << CONFIG_PCA9554_PHY0_RESET);
	pca953x_set_val(CONFIG_PCA9554_ADDR,
	  1 << CONFIG_PCA9554_PHY0_RESET, PCA953X_OUT_HIGH << CONFIG_PCA9554_PHY0_RESET);

	pca953x_set_dir(CONFIG_PCA9554_ADDR,
	  1 << CONFIG_PCA9554_PHY1_RESET, PCA953X_DIR_OUT << CONFIG_PCA9554_PHY1_RESET);
	pca953x_set_val(CONFIG_PCA9554_ADDR,
	  1 << CONFIG_PCA9554_PHY1_RESET, PCA953X_OUT_HIGH << CONFIG_PCA9554_PHY1_RESET);

	pca953x_set_dir(CONFIG_PCA9554_ADDR,
	  1 << CONFIG_PCA9554_SHT31_RESET, PCA953X_DIR_OUT << CONFIG_PCA9554_SHT31_RESET);
	pca953x_set_val(CONFIG_PCA9554_ADDR,
	  1 << CONFIG_PCA9554_SHT31_RESET, PCA953X_OUT_HIGH << CONFIG_PCA9554_SHT31_RESET);

	pca953x_set_dir(CONFIG_PCA9554_ADDR,
	  1 << CONFIG_PCA9554_LED, PCA953X_DIR_OUT << CONFIG_PCA9554_LED);
	pca953x_set_val(CONFIG_PCA9554_ADDR,
	  1 << CONFIG_PCA9554_LED, PCA953X_OUT_LOW << CONFIG_PCA9554_LED);
	#endif

	return 0;
}

#ifdef CONFIG_CMD_USB
#include <usb.h>
static int usb_inited = 0;

int ls1x_usb_init(void)
{
	if (!usb_inited) {
		/*end usb reset*/
#if defined(CONFIG_CPU_LOONGSON1A)
		/* enable USB */
		*(volatile int *)0xbfd00420 &= ~0x200000;
		/*ls1a usb reset stop*/
		*(volatile int *)0xbff10204 |= 0x40000000;
#elif defined(CONFIG_CPU_LOONGSON1B)
		/* enable USB */
		*(volatile int *)0xbfd00424 &= ~0x800;
		/*ls1b usb reset stop*/
		*(volatile int *)0xbfd00424 |= 0x80000000;
#elif defined(CONFIG_CPU_LOONGSON1C)
		*(volatile int *)0xbfd00424 &= ~(1 << 31);
		udelay(100);
		*(volatile int *)0xbfd00424 |= (1 << 31);
#endif
		usb_inited = 1;
	}
	return 0;
}

#if defined(CONFIG_USB_OHCI_LS1X) && defined(CONFIG_SYS_USB_OHCI_BOARD_INIT)
int usb_board_init(void)
{
	ls1x_usb_init();
	return 0;
}

int usb_board_stop(void)
{
	return 0;
}

int usb_board_init_fail(void)
{
	return 0;
}
#endif

#ifdef CONFIG_USB_EHCI
int ehci_hcd_init(int index, struct ehci_hccr **hccr, struct ehci_hcor **hcor)
{
	ls1x_usb_init();
	*hccr = (struct ehci_hccr *)(LS1X_EHCI_BASE);
	*hcor = (struct ehci_hcor *)(LS1X_EHCI_BASE + 0x10);

	return 0;
}

int ehci_hcd_stop(int index)
{
	return 0;
}
#endif
#endif

#if defined(CONFIG_MISC_INIT_R)
#if defined(CONFIG_CMD_EEPROM)
#define LS_BOARD_MAXLAN 32
#define LS_BOARD_ID_LAN 20
#define LS_BOARD_SN_LAN 22

int get_eeprom_info(void)
{
	uchar *datap = malloc(LS_BOARD_MAXLAN);
	int addr = CONFIG_SYS_I2C_EEPROM_ADDR;
	int ret;

	memset(datap, 0, LS_BOARD_MAXLAN);

	ret = eeprom_read(addr, 0, datap, LS_BOARD_ID_LAN);
	if (ret) {
		printf("Cannot read I2C EEPROM.\n");
		return 0;
	}

	if (strncmp((const char *)datap + 3, "LSGD", 4) == 0) {
		printf("Board: %s\n", datap);
		//get SN
		memset(datap, 0, LS_BOARD_MAXLAN);
		eeprom_read(addr, 32, datap, LS_BOARD_SN_LAN);
		printf("SN: %s\n", datap);
		//get mac addr 0
		memset(datap, 0, LS_BOARD_MAXLAN);
		eeprom_read(addr, 64, datap, 16);
		if (is_valid_ether_addr(datap))
			eth_setenv_enetaddr("ethaddr", datap);
		//get mac addr 1
		memset(datap, 0, LS_BOARD_MAXLAN);
		eeprom_read(addr, 64 + 16, datap, 16);
		if (is_valid_ether_addr(datap))
			eth_setenv_enetaddr("eth1addr", datap);
	} else {
		printf("Board: %s\n", CONFIG_BOARD_NAME);
	}

	return 0;
}
#endif

extern int tlb_init_default(void);

int misc_init_r(void)
{
	tlb_init_default();
#if defined(CONFIG_CMD_EEPROM)
	get_eeprom_info();
#endif
#ifdef BEEP_GPIO
	{
	int ret;
	ret = gpio_request(BEEP_GPIO, "beep");
	if (ret == 0) {
		int i;
		for (i=0; i<500; i++) {
			gpio_direction_output(BEEP_GPIO, i & 0x1);
			udelay(220);
		}
		gpio_direction_output(BEEP_GPIO, 0);
	}
	}
#endif

	return 0;
}
#endif
